package gpeerreview.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.IncrementalCommand;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.http.client.RequestException;
//import com.google.gwt.user.client.ui.ProgressBar;
//import java.math.BigInteger;
//import com.googlecode.gwt.math.Math;

public class EndorseTool implements EntryPoint
{
	private class Board implements ClickListener, MouseListener, KeyboardListener
	{
		private FocusPanel m_main;
		private Grid m_formGrid;

		private TextBox m_documentUrl;
		private TextArea m_comments;
		private TextBox m_score;
		private TextBox m_name;
		private TextBox m_cvUrl;
		private TextArea m_privateKey;
		private Label m_pleasePasteKey;
		private Button m_generate;
		private int m_state;
		private Button m_foundIt;
		private Grid m_generateGrid;
		private Label m_entropyProgress;
		private Button m_sign;
		private TextArea m_signedEndorsement;

		private final int entropy_part = 256;
		private final int entropy_target_size = entropy_part * 3;
		private byte[] m_entropy;
		int m_entropyPos;
		int m_entropyCount;
		java.util.Date m_date;
		private KeyPairFactory m_kpf;
		private KeyPair m_kp;
		private HttpClient m_client;

		Board()
		{
			m_formGrid = new Grid(15/*rows*/, 1/*cols*/);
			m_main = new FocusPanel(m_formGrid);

			m_formGrid.setWidget(0, 0, new Label("Enter the URL of the work you wish to endorse. (If it has not yet been published, or if it is not freely available, then those issues should be resolved first.)"));
			m_documentUrl = new TextBox();
			m_documentUrl.setVisibleLength(100);
			m_formGrid.setWidget(1, 0, m_documentUrl);

			m_formGrid.setWidget(2, 0, new Label("Enter comments about the work here. (Perhaps you might want to comment on the significance, correctness, novelty, quality, or potential of the work. Suggestions for improvement should be communicated to the author privately.)"));
			m_comments = new TextArea();
			m_comments.setCharacterWidth(75);
			m_comments.setVisibleLines(10);
			m_formGrid.setWidget(3, 0, m_comments);

			m_formGrid.setWidget(4, 0, new Label("On a scale from 0 to 1, to what extent do you endorse this work?"));
			m_score = new TextBox();
			m_score.setVisibleLength(40);
			m_formGrid.setWidget(5, 0, m_score);

			m_formGrid.setWidget(6, 0, new Label("What is your name?"));
			m_name = new TextBox();
			m_name.setVisibleLength(100);
			m_formGrid.setWidget(7, 0, m_name);

			m_formGrid.setWidget(8, 0, new Label("What is the URL of your c.v.? (Ideally, you would display signed endorsements of your works at this URL, so that others may assess your credibility. If you don't have any, the URL of your traditional-style c.v. will suffice.)"));
			m_cvUrl = new TextBox();
			m_cvUrl.setVisibleLength(100);
			m_formGrid.setWidget(9, 0, m_cvUrl);

			m_pleasePasteKey = new Label("Please paste your private key into this field. (It will not be transmitted. If you want to be sure, you can switch to offline mode now by clicking File->Work Offline.) If you don't have a private key, click on \"Generate a New Key-Pair\".");
			m_formGrid.setWidget(10, 0, m_pleasePasteKey);
			m_privateKey = new TextArea();
			m_privateKey.setCharacterWidth(75);
			m_privateKey.setVisibleLines(5);
			m_formGrid.setWidget(11, 0, m_privateKey);
			m_generate = new Button("Generate a New Key-Pair", this);
			m_formGrid.setWidget(12, 0, m_generate);

			m_sign = new Button("Sign the endorsement", this);
			m_formGrid.setWidget(13, 0, m_sign);

			m_main.addMouseListener(this);
			m_main.addKeyboardListener(this);
			m_entropy = new byte[entropy_target_size];
			m_date = new java.util.Date();
			m_state = 0;
			m_client = new HttpClient();
		}

		Widget getWidget()
		{
			return m_main;
		}

		private String getPercentEntropyString()
		{
			float f = (float)m_entropyCount * 100 / entropy_target_size;
			int n = (int)(f * 100);
			return "" + ((float)n / 100) + "%";
		}

		public void onClick(Widget sender)
		{
			if(sender == m_generate)
			{
				if(m_foundIt == null)
				{
					m_foundIt = new Button("Oh, I found my private key", this);
					m_generateGrid = new Grid(6/*rows*/, 1/*cols*/);
					m_generateGrid.setWidget(0, 0, new Label("Please wiggle your mouse until the number below reaches 100%. (We must collect entropy in order to generate a secure key-pair.)"));
					m_entropyProgress = new Label(getPercentEntropyString());
					m_generateGrid.setWidget(1, 0, m_entropyProgress);
				}
				m_formGrid.setWidget(10, 0, m_foundIt);
				m_formGrid.setWidget(11, 0, m_generateGrid);
				m_formGrid.setWidget(12, 0, new Label(""));
				m_state = 1;
				if(m_entropyCount == entropy_target_size)
					generateNewKeyPair();
			}
			else if(sender == m_foundIt)
			{
				m_formGrid.setWidget(10, 0, m_pleasePasteKey);
				m_formGrid.setWidget(11, 0, m_privateKey);
				m_formGrid.setWidget(12, 0, m_generate);
				m_state = 0;
			}
			else if(sender == m_sign)
				downloadAndSignEndorsement();
			else
				Window.alert("Unrecognized button");
		}

		private void generateNewKeyPair()
		{
			// Warn the user that this is going to take forever
			m_generateGrid.setWidget(0, 0, new Label("Generating a new key-pair. Depending on the speed of your computer, this may take as long as an hour or more..."));
			Label status = new Label("Status");
			m_generateGrid.setWidget(1, 0, status);
			Label progress = new Label("0%");
			m_generateGrid.setWidget(2, 0, progress);
			m_kpf = new KeyPairFactory(m_entropy, status, progress);

			// Call kpf.crunch() in a loop until it returns true.
			// (Yield to the browser between calls, so it can refresh
			// the interface, and so it doesn't think the script
			// has stopped responding.)
			IncrementalCommand ic = new IncrementalCommand(){
				public boolean execute() {
					if(m_kpf.crunch() && m_state > 0)
					{
						onDoneGeneratingKeyPair(m_kpf.getResults());
						return false;
					}
					else
						return true;
				}
			};
			DeferredCommand.addCommand(ic);
		}

		private void onDoneGeneratingKeyPair(KeyPair kp)
		{
			Window.alert("Done generating your key-pair! Please store it in a secure location.");
			m_formGrid.setWidget(10, 0, m_pleasePasteKey);
			m_formGrid.setWidget(11, 0, m_privateKey);
			m_formGrid.setWidget(12, 0, m_generate);
			m_privateKey.setText(kp.privateToString());
			m_state = 0;
			m_entropyCount = 0;
			m_entropyPos = 0;
		}

		private void addEntropy(byte b)
		{
			m_entropy[m_entropyPos] ^= b;
			if(++m_entropyPos >= entropy_target_size)
				m_entropyPos = 0;
			if(m_entropyCount < entropy_target_size)
			{
				m_entropyCount++;
				if(m_entropyProgress != null)
					m_entropyProgress.setText(getPercentEntropyString());
				if(m_entropyCount == entropy_target_size && m_state > 0)
					generateNewKeyPair();
			}
		}

		private void downloadAndSignEndorsement()
		{
			// Request the document
			try
			{
				m_client.getUrl(m_documentUrl.getText());
			}
			catch(RequestException e)
			{
				Window.alert("Error while downloading document: " + m_documentUrl.getText() + "\n" + e.toString());
				return;
			}

			// Deserialize the key pair
			m_kp = new KeyPair(m_privateKey.getText());
			if(m_kp.m_private == null)
			{
				Window.alert("Invalid private key. Please paste your private key above and try again.");
				return;
			}

			// Wait until the document finishes downloading
			IncrementalCommand ic = new IncrementalCommand(){
				public boolean execute() {
					if(m_client.isPending())
						return true;
					else
					{
						if(m_client.m_error != null)
							Window.alert(m_client.m_error);
						else if(m_client.m_results == null)
							Window.alert("Unknown problem downloading document");
						else
							signEndorsement();
						return false;
					}
				}
			};
			DeferredCommand.addCommand(ic);
		}

		private byte[] stringToByteArray(String s)
		{
			byte[] b = new byte[s.length()];
			for(int i = 0; i < s.length(); i++)
				b[i] = (byte)s.charAt(i);
			return b;
		}

		public String byteArrayToHexadecimal(byte[] bytes)
		{
			String s = "";
			for(int i = 0; i < bytes.length; i++)
			{
				int msn = (0xf0 & bytes[i]) >> 4;
				s += (char)(msn >= 10 ? ('a' + msn - 10) : ('0' + msn));
				int lsn = 0x0f & bytes[i];
				s += (char)(lsn >= 10 ? ('a' + lsn - 10) : ('0' + lsn));
			}
			return s;
		}

		private void signEndorsement()
		{
			// hash the document
			byte[] doc = stringToByteArray(m_client.m_results);
			sha256 hasher = new sha256();
			byte[] docHash = hasher.hash(doc);

			// Make the endorsement
			String s = "-----Begin Signed Endorsement-----\n";
			s += "Document: " + m_documentUrl.getText() + "\n";
			s += "Endorser: " + m_name.getText() + "\n";
			s += "Endorser's CV: " + m_cvUrl.getText() + "\n";
			s += "Extent: " + m_score.getText() + "\n";
			s += "\n";
			s += m_comments.getText() + "\n";
			s += "\n";
			s += byteArrayToHexadecimal(docHash) + "\n";
			s += "\n";
			byte[] sig = m_kp.Sign(stringToByteArray(s));
			s += byteArrayToHexadecimal(sig) + "\n";
			s += "-----End Signed Endorsement-----\n";

			// Display the results
			m_signedEndorsement = new TextArea();
			m_signedEndorsement.setCharacterWidth(75);
			m_signedEndorsement.setVisibleLines(5);
			m_signedEndorsement.setText(s);
			m_formGrid.setWidget(14, 0, m_signedEndorsement);
		}

		public void onMouseDown(Widget sender, int x, int y)
		{
		}

		public void onMouseUp(Widget sender, int x, int y)
		{
		}

		public void onMouseEnter(Widget sender)
		{
		}

		public void onMouseLeave(Widget sender)
		{
		}

		public void onMouseMove(Widget sender, int x, int y)
		{
			addEntropy((byte)(x ^ y ^ (int)m_date.getTime() ^ Random.nextInt()));
		}

		public void onKeyDown(Widget sender, char keyCode, int modifiers)
		{
		}

		public void onKeyUp(Widget sender, char keyCode, int modifiers)
		{
		}

		public void onKeyPress(Widget sender, char keyCode, int modifiers)
		{
			addEntropy((byte)((int)m_date.getTime() ^ (int)keyCode ^ Random.nextInt()));
		}
	}

	static String byteArrayToString(byte[] arr)
	{
		String s = "";
		for(int i = 0; i < arr.length; i++)
			s += (char)arr[i];
		return s;
	}

	public void TestHash()
	{
/*
byte[] entropy = new byte[3];
entropy[0] = 0x14;
entropy[1] = (byte)0x99;
entropy[2] = 0x01;
BigInt.PrimeMaker pm = BigInt.ZERO.new PrimeMaker(entropy, 1000);
while(!pm.crunch())
{
}
Window.alert("The result is: " + pm.getResults());
*/




		byte[] block = new byte[10];
		for(int i = 0; i < 10; i++)
			block[i] = (byte)('a' + i);
		sha256 sha = new sha256();
		byte[] digest = sha.hash(block);
		//Window.alert(s);
	}

	public void TestKeyPair()
	{
/*		KeyPair kp = new KeyPair();
		byte[] arr1 = new byte[2]; arr1[0] = (byte)44; arr1[1] = (byte)123;
		byte[] arr2 = new byte[2]; arr2[0] = (byte)88; arr2[1] = (byte)41;
		byte[] arr3 = new byte[2]; arr3[0] = (byte)150; arr3[1] = (byte)73;
		kp.GenerateKeyPair(arr1, arr2, arr3);
		int textBlockSize = kp.GetTextBlockSize();
		byte[] in = new byte[textBlockSize];
		for(int i = 0; i < textBlockSize; i++)
			in[i] = (byte)('a' + i);
		byte[] signature = kp.Sign(in);
		byte[] ungarbled = kp.Authenticate(signature);
Window.alert("About to check results");
		if(ungarbled.length != in.length)
		{
			Window.alert("wrong length");
			throw new IllegalArgumentException("wrong length");
		}
Window.alert("Orig=" + byteArrayToString(in) + "   Sig=" + byteArrayToString(signature) + "   Ungarbled=" + byteArrayToString(ungarbled));
		for(int i = 0; i < ungarbled.length; i++)
		{
			if(ungarbled[i] != (byte)('a' + i))
				throw new IllegalArgumentException("failed to decode");
		}
		Window.alert("KeyPair is working properly!");*/
	}

	private Grid m_mainGrid;
	private Board m_board;

	public void onModuleLoad()
	{
		m_mainGrid = new Grid(1/*rows*/, 1/*cols*/);
		m_board = new Board();
		m_mainGrid.setWidget(0, 0, m_board.getWidget());
		RootPanel.get().add(m_mainGrid);
	}
}
